//
//  NetworkManager.m
//  Soccer
//
//  Created by allm on 09. 06. 01.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "NetworkManager.h"
#import "ClientManager.h"
#import "Lobby.h"
#import "Match.h"
#import "GameScene.h"
#import "Constant.h"

//CONSTANTS:

// The Bonjour application protocol, which must:
// 1) be no longer than 14 characters
// 2) contain only lower-case letters, digits, and hyphens
// 3) begin and end with lower-case letter or digit
// It should also be descriptive and human-readable
// See the following for more information:
// http://developer.apple.com/networking/bonjour/faq.html
#define kGameIdentifier		@"iPhoneSoccer"

//INTERFACES:

@interface NetworkManager ()
- (void) setup;
- (void) presentLobby:(NSString*)name;
- (void) presentGameRoom:(NSString*)name;
- (void) sendStartMessage;
- (void) startGame;
- (void) sendHeader:(const struct FrameHeader *)message toStream:(NSOutputStream *)outStream;
- (void) sendHeader:(const struct FrameHeader *)fh withData:(const struct FrameData *)fd toStream:(NSOutputStream *)outStream;
@end

//CLASS IMPLEMENTATIONS:

@implementation NetworkManager

@synthesize networkInterval;
@synthesize networkMode;

//@synthesize match;

static NetworkManager *_sharedNetworkManager = nil;

+ (NetworkManager *)sharedNetworkManager
{
	@synchronized([NetworkManager class])
	{
		if (!_sharedNetworkManager)
			[[self alloc] init];
		
		return _sharedNetworkManager;
	}
	// to avoid compiler warning
	return nil;
}

+ (id)alloc
{
	@synchronized([NetworkManager class])
	{
		NSAssert(_sharedNetworkManager == nil, @"Attempted to allocate a second instance of a singleton.");
		_sharedNetworkManager = [super alloc];
		return _sharedNetworkManager;
	}
	// to avoid compiler warning
	return nil;
}

- (id)init
{	
	if (self = [super init])
	{
		networkMode = ClientMode;
		initialSetting = NO;
		gameStarted = NO;
		inputStreams = [[NSMutableArray arrayWithCapacity:5] retain];
		outputStreams = [[NSMutableArray arrayWithCapacity:5] retain];
		
		// 서버의 스트림 자리에 임시 오브젝트를 삽입해 둔다
		[inputStreams insertObject:@"NULL" atIndex:0];
		[outputStreams insertObject:@"NULL" atIndex:0];
		
		inputReady = [[NSMutableArray arrayWithCapacity:5] retain];
		outputReady = [[NSMutableArray arrayWithCapacity:5] retain];
		[inputReady insertObject:@"NO" atIndex:0];
		[outputReady insertObject:@"NO" atIndex:0];
		
		networkInterval = 1.0 / networkFrameRate;
		frameNumber = 0;
		preFrameNumber = -1;
		
		// Sound 생성
		shootSound = [[PASoundMgr sharedSoundManager] addSound:@"soccer_kick" withExtension:@"wav" position:CGPointZero looped:NO];
		stealSound = [[PASoundMgr sharedSoundManager] addSound:@"hey" withExtension:@"wav" position:CGPointZero looped:NO];
		stunSound = [[PASoundMgr sharedSoundManager] addSound:@"stun" withExtension:@"wav" position:CGPointZero looped:NO];
	}
	
	return self;
}

- (void) dealloc
{	
	[_inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	[_inStream release];	
	[_outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	[_outStream release];
	[_server release];
	[_lobby release];
	
	for (NSInputStream *stream in inputStreams)
	{
		[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	}
	[inputStreams release];
	
	for (NSOutputStream *stream in outputStreams)
	{
		[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
	}
	[outputStreams release];
	[inputReady release];
	[outputReady release];
	
	[super dealloc];
}


- (void) _showAlert:(NSString*)title type:(int)type
{
	UIAlertView* alertView;
	
	// 에러메시지
	if(type == 0)
	{
		alertView = [[UIAlertView alloc] initWithTitle:title message:@"All-m iPhone Soccer" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	}
	else if(type == 1)
	{
		[networkTimer invalidate];
		[[Director sharedDirector] stopAnimation];
		
		NSString *s1, *s2;
		int goal_a = [[ClientManager sharedManager].match getGoalA];
		int goal_b = [[ClientManager sharedManager].match getGoalB];
		
		s2 = [NSString stringWithFormat:@"Red %i : %i Blue", goal_a, goal_b];
		
		if(goal_a > goal_b)
		{
			s1 = @"Red Team Win!";			
		}
		else if(goal_a == goal_b)
		{
			s1 = @"Draw Game!";
		}
		else
		{
			s1 = @"Blue Team Win!";
		}
		
		alertView = [[UIAlertView alloc] initWithTitle:s1 message:s2 delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	}
	
	[alertView show];
	[alertView release];
}

// 네트워크 세팅 시작
- (void) networkSetting
{	
	[self presentLobby:nil];
}

// Setup은 자신이 만든 서버를 advertising하는 곳으로 서버 역할을 할 때만 호출 된다
- (void) setup
{
	networkMode = ServerMode;
	
	[_server release];
	_server = nil;
	
	_server = [TCPServer new];
	[_server setDelegate:self];
	
	NSError* error;
	if(_server == nil || ![_server start:&error]) {
		NSLog(@"Failed creating server: %@", error);
		[self _showAlert:@"Failed creating server" type:0];
		return;
	}
	
	//Start advertising to clients, passing nil for the name to tell Bonjour to pick use default name
	if(![_server enableBonjourWithDomain:@"local" applicationProtocol:[TCPServer bonjourTypeFromIdentifier:kGameIdentifier] name:nil]) {
		[self _showAlert:@"Failed advertising server" type:0];
		return;
	}
	
	// 그리고 자신의 플레이어 객체를 생성
	[[ClientManager sharedManager] InsertPlayer:@"Myself" index:0 team:0 isMine:YES];
	
	// temp
	//[[ClientManager sharedManager] InsertPlayer:@"test" index:1 team:1 isMine:NO];
	
	[self presentGameRoom:nil];
	
	// 게임룸에 자신을 표시
	[_gameRoom InsertPlayer:@"Myself" ofId:0];
}

// Make sure to let the user know what name is being used for Bonjour advertisement.
// This way, other players can browse for and connect to this game.
// Note that this may be called while the alert is already being displayed, as
// Bonjour may detect a name conflict and rename dynamically.
- (void) presentLobby:(NSString*)name 
{
	if (!_lobby)
	{
		_lobby = [[Lobby alloc] initWithFrame:CGRectMake(0, 0, 480, 320) type:[TCPServer bonjourTypeFromIdentifier:kGameIdentifier]];
		_lobby.delegate = self;
	}
	
	_lobby.gameName = name;
	
	if (!_lobby.superview) 
	{
		[[Director sharedDirector].openGLView addSubview:_lobby];
	}
}

- (void) destroyLobby
{
	[_lobby removeFromSuperview];
	[_lobby release];
	_lobby = nil;
}

- (void) presentGameRoom:(NSString*)name
{
	if (!_gameRoom) 
	{
		_gameRoom = [[GameRoom alloc] initWithFrame:CGRectMake(0, 0, 480, 320) type:[TCPServer bonjourTypeFromIdentifier:kGameIdentifier]];
		if (networkMode == ClientMode)
			[_gameRoom removeButton];
	}
	
	
	if (!_gameRoom.superview) 
	{
		[[Director sharedDirector].openGLView addSubview:_gameRoom];
	}
}

- (void) destroyGameRoom 
{
	[_gameRoom removeFromSuperview];
	[_gameRoom release];
	_gameRoom = nil;
	
	// GameRoom을 없애는 순간 게임이 시작된다고 가정
	gameStarted = YES;
}

- (void) initializeGame
{	
	[[ClientManager sharedManager] reset];
	[self init];
	
	GameScene *gs = [GameScene node];
	[[Director sharedDirector] replaceScene:gs];
	
	[[Director sharedDirector] startAnimation];
	
	gameStarted = NO;
}

- (void) startGame
{
	// 서버는 각 유저에게 시작 메시지를 보낸다
	if(networkMode == ServerMode)
		[self sendStartMessage];
	
	[_server release];
	_server = nil;
	
	[self destroyLobby];
	[self destroyGameRoom];
	[[ClientManager sharedManager] InsertBall];							// 화면에 공을 표시
	[[ClientManager sharedManager] InsertGoalKeeper:0];					// 화면에 골키퍼 표시
	[[ClientManager	sharedManager] InsertGoalKeeper:1];
	
	networkTimer = [NSTimer scheduledTimerWithTimeInterval:networkInterval target:self selector:@selector(beginNetworkLoop) userInfo:nil repeats:YES];
}	

// 게임이 시작한다는 메시지를 보냄
- (void) sendStartMessage
{
	if(networkMode == ServerMode)
	{
		struct FrameHeader fh;
		fh.h_frame_id = 4;
		fh.h_send_id = 0;
		fh.h_recv_id = 0;		// 브로드캐스트 메시지이므로 리시버 아이디를 지정할 필요 없음
		
		for(NSOutputStream *oStream in outputStreams)
		{
			if(oStream != @"NULL")
			{
				[self sendHeader:&fh toStream:oStream];
			}
		}
	}
}

// 게임을 종료한다는 메시지를 보냄
- (void) sendEndMessage
{
	if(networkMode == ServerMode)
	{
		struct FrameHeader fh;
		fh.h_frame_id = 11;
		fh.h_send_id = 0;
		fh.h_recv_id = 0;		// 브로드캐스트 메시지이므로 리시버 아이디를 지정할 필요 없음
		
		for(NSOutputStream *oStream in outputStreams)
		{
			if(oStream != @"NULL")
			{
				[self sendHeader:&fh toStream:oStream];
			}
		}
	}
}

- (unsigned long) timeGetTime
{
	static struct timeval tv;
	static unsigned long time;
	gettimeofday(&tv, nil);
	
	time = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
	return time;
}

- (void) beginNetworkLoop
{
	++frameNumber;

	// 클라이언트는 자신의 정보만 서버에 보낸다
	if(networkMode == ClientMode)
	{
		struct FrameHeader fh;
		struct FrameData fd;
		
		fh.h_frame_id		= 99;				// Header Message가 아닐 경우
		fh.h_continue		= YES;
		
		fd.d_frame_id		= 2;				// 2: 클라이언트에서 서버에 자신의 정보를 전달하는 메시지
		fd.d_frame_number	= frameNumber;
		
		fd.d_send_id		= 1;				// 나중에 잘 돌아가나 체크할 필요 있음
		fd.d_recv_id		= 0;				// 무조건 서버에 보낸다
		
		//		if ([[ClientManager sharedManager].match getBallOwner] == 1)
		//			fd.d_ball_owner	= 1;
		//		else
		//			fd.d_ball_owner = -1;
		//		if ([[ClientManager sharedManager].match getBallShooter] == 1)
		//			fd.d_ball_shooter	= 1;
		//		else
		//			fd.d_ball_shooter	= -1;
		//		fd.d_ball_pos_x		= [[ClientManager sharedManager].match getBallPosition].x;
		//		fd.d_ball_pos_y		= [[ClientManager sharedManager].match getBallPosition].y;
		//		fd.d_ball_dir		= [[ClientManager sharedManager].match getBallDirection];
		//		fd.d_ball_shoot_time = [[ClientManager sharedManager].match getBallShootTime];
		
		// 이 부분을 이렇게 처리하기 위해서는 모든 플레이어가 같은 플레이어 인덱스를 가지고 있어야 한다
		// 1:1을 가정하므로 일단 플레이어1의 정보만 보내면 된다.
		fd.d_player0_id		= 1;
		fd.d_player0_pos_x	= [[ClientManager sharedManager].match getPlayerPositionAt:1].x;
		fd.d_player0_pos_y	= [[ClientManager sharedManager].match getPlayerPositionAt:1].y;
		fd.d_player0_dir	= [[ClientManager sharedManager].match getPlayerDirectionAt:1];
		fd.d_player0_status	= [[ClientManager sharedManager].match getPlayerStatusAt:1];
		
		[self sendHeader:&fh withData:&fd toStream:_outStream];
	}
	// 서버는 볼과 모든 플레이어의 정보를 브로드 캐스팅한다.
	else
	{
		// 프레이 보낼 때의 시간 체크
		unsigned long server_time = [self timeGetTime];
		// NSLog([NSString stringWithFormat:@"%lu", server_time]);
		
		// 3분 후 게임종료
		if(frameNumber == endTime)
		{
			[self sendEndMessage];
			[self _showAlert:@"Game finished" type:1];
			return;
		}
		
		
		[[ClientManager sharedManager].match setGameTime:frameNumber];
		
		struct FrameHeader fh;
		struct FrameData fd;
		
		fh.h_frame_id		= 99;				// Header Message가 아닐 경우
		fh.h_time			= server_time;
		fh.h_continue		= YES;
		
		fd.d_frame_id		= 1;				// 1: 서버에서 모든 유저에게 각 유저의 정보에 대해 브로드 캐스팅 해주는 메시지
		fd.d_frame_number	= frameNumber;
		
		fd.d_send_id		= 0;
		fd.d_recv_id		= 1;				// 일단 1대 1을 가정한다
		
		fd.d_ball_owner		= [[ClientManager sharedManager].match getBallOwner];
		fd.d_ball_shooter	= [[ClientManager sharedManager].match getBallShooter];
		fd.d_ball_pos_x		= [[ClientManager sharedManager].match getBallPosition].x;
		fd.d_ball_pos_y		= [[ClientManager sharedManager].match getBallPosition].y;
		fd.d_ball_dir		= [[ClientManager sharedManager].match getBallDirection];
		
		// 이 부분을 이렇게 처리하기 위해서는 모든 플레이어가 같은 플레이어 인덱스를 가지고 있어야 한다
		// 1:1을 가정하므로 일단 서버 플레이어의 정보만 보내면 된다.
		fd.d_player0_id		= 0;
		fd.d_player0_pos_x	= [[ClientManager sharedManager].match getPlayerPositionAt:0].x;
		fd.d_player0_pos_y	= [[ClientManager sharedManager].match getPlayerPositionAt:0].y;
		fd.d_player0_dir	= [[ClientManager sharedManager].match getPlayerDirectionAt:0];
		fd.d_player0_status	= [[ClientManager sharedManager].match getPlayerStatusAt:0];
		
		for(NSOutputStream *oStream in outputStreams)
		{
			if(oStream != @"NULL")
			{
				[self sendHeader:&fh withData:&fd toStream:oStream];
			}
		}
	}
}

// If we display an error or an alert that the remote disconnected, handle dismissal and return to setup
- (void) alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
	[self initializeGame];
	//[self networkSetting];
}

// Sending Header Frame
- (void) sendHeader:(const struct FrameHeader *)message toStream:(NSOutputStream *)outStream
{
	if (outStream && [outStream hasSpaceAvailable])
		if([outStream write:(const uint8_t *)message maxLength:sizeof(struct FrameHeader)] == -1)
			[self _showAlert:@"Failed sending data to peer" type:0];
}

// Sending Header & Data Frame
- (void) sendHeader:(const struct FrameHeader *)fh withData:(const struct FrameData *)fd toStream:(NSOutputStream *)outStream
{
	if (outStream && [outStream hasSpaceAvailable])
	{
		if([outStream write:(const uint8_t *)fh maxLength:sizeof(struct FrameHeader)] == -1 || [outStream write:(const uint8_t *)fd maxLength:sizeof(struct FrameData)] == -1)
		{
			[self _showAlert:@"Failed sending data to peer" type:0];
		}
	}
}

// Receiving Header Frame
- (struct FrameHeader) recvHeader:(NSInputStream *)inStream
{
	struct FrameHeader fh;
	
	unsigned int length = 0;
	length = [inStream read:(uint8_t *)&fh maxLength:sizeof(struct FrameHeader)];
	
	if(length == 0)
	{
		if([inStream streamStatus] != NSStreamStatusAtEnd)
			[self _showAlert:@"Failed reading data" type:0];
	}
	
	return fh;
}

// Receiving Data Frame
- (struct FrameData) recvData:(NSInputStream *)inStream
{
	struct FrameData fd;
	
	unsigned int length = 0;
	length = [inStream read:(uint8_t *)&fd maxLength:sizeof(struct FrameData)];
	
	if(length == 0)
	{
		if([inStream streamStatus] != NSStreamStatusAtEnd)
			[self _showAlert:@"Failed reading data" type:0];
	}
	
	return fd;
}

// In/Out Stream을 연다
- (void) openInputStream:(NSInputStream *)inStream outputStream:(NSOutputStream *)outStream 
{
	inStream.delegate = self;
	[inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[inStream open];
	
	outStream.delegate = self;
	[outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[outStream open];
}


// 클라이언트가 서버에 접속하는 순간
- (void) browserViewController:(BrowserViewController*)bvc didResolveInstance:(NSNetService*)netService
{
	if (!netService) {
		//		[self setup];
		return;
	}
	
	if (![netService getInputStream:&_inStream outputStream:&_outStream]) {
		[self _showAlert:@"Failed connecting to server" type:0];
		return;
	}
	[_inStream retain];
	[_outStream retain];
	
	[self openInputStream:_inStream outputStream:_outStream];
}

- (void) goalHappened:(int)goal
{
	// 서버인 경우만 골 메시지를 클라이언트들에게 브로드캐스팅한다.
	if(networkMode != ServerMode)
		return;
	
	[[ClientManager sharedManager].match setGoalIn:goal];
	
	struct FrameHeader fh;
	
	fh.h_frame_id		= 5;				// Header Message가 아닐 경우
	fh.h_continue		= NO;
	
	// 혼란의 여지가 있지만 누가 골을 넣었는지 d_send_id에 넣어서 보낸다
	fh.h_send_id		= goal;
	fh.h_recv_id		= 1;				// 일단 1대 1을 가정한다
	
	for(NSOutputStream *oStream in outputStreams)
	{
		if(oStream != @"NULL")
		{
			[self sendHeader:&fh toStream:oStream];
		}
	}
}

- (void) shootHappened
{
	// 서버가 슛한 경우는 네트워크를 통해 데이타 전송할 필요없다. 직접 자신의 정보 수정한다.
	if(networkMode == ServerMode)
	{
		// Ball owner, shooter 정보 갱신
		[[ClientManager sharedManager].match setBallShooter: [[ClientManager sharedManager].match getBallOwner]];
		// Ball direction 갱신
		[[ClientManager sharedManager].match setBallDirection:[[ClientManager sharedManager].match getPlayerDirectionAt:0]];
		// Shoot sound 재생
		[shootSound playAtListenerPosition];
		return;
	}
	
	struct FrameHeader fh;
	
	fh.h_frame_id		= 6;				// Header Message가 아닐 경우
	fh.h_send_id		= 1;
	fh.h_continue		= NO;
	
	[self sendHeader:&fh toStream:_outStream];
	
	// 클라이언트가 슛한 경우. 슛한다는 메시지를 서버로로 전송하고 나서 바로 슛 사운드를 재생해준다.
	// 슛 메시지를 서버로 전송한 이후, 서버에서 오는 프레임을 보고 슛이 발생했음을 판단하는 것이 시점상 가장 정확하겠지만
	// 별 차이가 없을 것이라 생각하고 여기서 사운드 재생한다.
	// 그러므로 혹시 테스트 후 부자연스러울 경우에는 수정 필요함.
	[shootSound playAtListenerPosition];
	

}

- (void) stealHappened
{
	Match* match = [ClientManager sharedManager].match;
	Player* myself = match.myself;
	
	int ballOwnerIndex = [[ClientManager sharedManager].match getBallOwner];
	// Ball owner가 없는 상황. 스틸할 수 없다.
	if(ballOwnerIndex == -1)
		return;
	Player *ballOwner = [match.players objectAtIndex:ballOwnerIndex];
	
	// 서버가 스틸한 경우는 네트워크를 통해 데이타 전송할 필요없다. 직접 자신의 정보 수정한다.
	if(networkMode == ServerMode)
	{
		double distance = [myself getDistanceFrom:myself.position To:ballOwner.position];
		
		if (distance <= stealRange)
		{
			[[ClientManager sharedManager].match setBallOwner: [match.players indexOfObject:myself]];
			
			// 스틸 사운드 재생
			[stealSound playAtListenerPosition];
		}
		
		return;
	}
	
	struct FrameHeader fh;
	
	fh.h_frame_id		= 7;				// Header Message가 아닐 경우
	fh.h_send_id		= 1;				// 무조건 client. 서버는 여기 도달할 수 없다.
	fh.h_continue		= NO;
	
	[self sendHeader:&fh toStream:_outStream];
}

- (void) skillHappened
{
	Match* match = [ClientManager sharedManager].match;
	
	// 서버가 스킬을 사용한 경우는 네트워크를 통해 데이타 전송할 필요없다. 직접 자신의 정보 수정한다.
	if(networkMode == ServerMode)
	{
		CGPoint myPos = match.myself.position;
		
		// 스킬을 사용한 플레이어 근처의 선수들 파악
		NSMutableArray *players = match.players;
		
		for (Player *tempPlayer in players)
		{
			if (tempPlayer.status == Stun)
				continue;
			if (tempPlayer == match.myself)
				continue;
			if ([tempPlayer isKindOfClass:[GoalKeeper class]])
				continue;
			
			double distance = [tempPlayer getDistanceFrom:tempPlayer.position To:myPos];
			
			if (distance <= stunRange)
			{
				// 스턴 걸린 플레이어들에게 알려준다.
				struct FrameHeader fh;
				
				fh.h_frame_id		= 9;				// Header Message가 아닐 경우
				fh.h_continue		= NO;
				
				int clientIndex = 1;
				NSOutputStream *oStream = [outputStreams objectAtIndex:clientIndex];
				[self sendHeader:&fh toStream:oStream];
				
				[stunSound playAtListenerPosition];
				
				// 서버에서 해당 선수들의 상태를 스턴으로 바꾼다.(서버에서 스턴걸린 이미지로 보기 위해서)
				tempPlayer.status = Stun;
				tempPlayer.stunDurationByFrame = stunDuration;
			}
		}
		
		match.shakedNum = 0;
		match.initDate = nil;
		
		return;
	}
	
	struct FrameHeader fh;
	
	fh.h_frame_id		= 8;				// Header Message가 아닐 경우
	fh.h_send_id		= 1;				// 무조건 client. 서버는 여기 도달할 수 없다.
	fh.h_continue		= NO;
	
	[self sendHeader:&fh toStream:_outStream];
}

@end


@implementation NetworkManager (NSStreamDelegate)

- (void) stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
	UIAlertView* alertView;
	switch(eventCode) 
	{
		case NSStreamEventOpenCompleted:
		{
			// Client의 경우
			if (networkMode == ClientMode)
			{
				[_server release];
				_server = nil;
				
				if (stream == _inStream)
					_inReady = YES;
				else
					_outReady = YES;
				
				if (_inReady && _outReady)
				{
					[self presentGameRoom:nil];
					[_gameRoom InsertPlayer:@"Server" ofId:0];
				}
				
			}
			// Server의 경우
			else
			{
				
			}
 			
			break;
		}
			// 쓸 때
		case NSStreamEventHasSpaceAvailable:
		{
			// 클라이언트
			if(networkMode == ClientMode)
			{
				
			}
			// 서버
			else
			{
				// 접속한 유저에 대한 초기 세팅, id값을 전송
				// 이부분도 1대1 가정
				if(initialSetting == NO)
				{
					int recvID;
					if ( [stream isKindOfClass:[NSOutputStream class]] )
					{
						recvID = [outputStreams indexOfObject:stream];
						//						[outputReady insertObject:@"YES" atIndex:recvID];
					}
					else
					{
						recvID = [inputStreams indexOfObject:stream];
						//						[inputReady insertObject:@"YES" atIndex:recvID];
					}
					
					struct FrameHeader fh;
					fh.h_frame_id	= 0;
					fh.h_send_id	= 0;
					fh.h_recv_id	= recvID;
					
					[self sendHeader:&fh toStream:[outputStreams objectAtIndex:recvID]];
					
					// 게임룸에 접속한 유저의 정보를 추가한다
					NSString *hisName = [NSString stringWithFormat:@"Player %i", recvID];
					[_gameRoom InsertPlayer:hisName ofId:recvID];
					
					// 접속한 유저의 플레이어 객체를 추가한다
					[[ClientManager sharedManager] InsertPlayer:hisName index:recvID team:recvID%2 isMine:NO];
					
					initialSetting = YES;
				}
			}
			break;
		}
			// 읽을 때
		case NSStreamEventHasBytesAvailable:
		{
			if ([stream isKindOfClass:[NSInputStream class]])
			{
				struct FrameHeader fh = [self recvHeader:(NSInputStream *)stream];
				struct FrameData fd;
				if(fh.h_continue == YES)			// Header Frame을 읽은 후 뒤에 Data Frame이 따라오는지 확인한다
				{
					fd = [self recvData:(NSInputStream *)stream];
				}
				
				// Client의 경우 (서버에서 오는 메시지를 읽음)
				if(networkMode == ClientMode)
				{
					if(fh.h_frame_id == 0)
					{
						myId = fh.h_recv_id;
						myName = [NSString stringWithFormat:@"Player %i", myId];
						myTeam = myId % 2;
						
						// GameRoom에 자신의 정보를 표시 (클라이언트)
						[_gameRoom InsertPlayer:myName ofId:myId];
						
						// 서버의 Player 객체를 생성 (서버 플레이어는 무조건 존재하므로 이 때 만들어 준다)
						[[ClientManager sharedManager] InsertPlayer:@"Server" index:0 team:0 isMine:NO];
						
						// 자신의 Player 객체를 생성
						[[ClientManager sharedManager] InsertPlayer:myName index:myId team:myTeam isMine:YES];
					}
					else if(fh.h_frame_id == 4)
					{
						[self startGame];				
					}
					// 골인시
					else if(fh.h_frame_id == 5)
					{
						int goal = fh.h_send_id;
						
						[[ClientManager sharedManager].match setGoalIn:goal];
					}
					// 자신의 플레이어가 스킬을 당한 경우
					else if (fh.h_frame_id == 9)
					{						
						Match* match = [ClientManager sharedManager].match;
						
						[stunSound playAtListenerPosition];
						
						if(match.myself.status != Stun)
						{
							match.myself.status = Stun;
							match.myself.stunDurationByFrame = stunDuration;
						}
						
						// 스킬 사운드 발생함.
					}
					// 자신이 사용한 스킬이 효과적으로 적용된 경우
					else if(fh.h_frame_id == 10)
					{
						Match* match = [ClientManager sharedManager].match;
						
						// 서버 플레이어에게 스턴 효과를 적용한다.
						Player* server = [match.players objectAtIndex:0];
						server.status = Stun;
						server.stunDurationByFrame = stunDuration;
						
						[stunSound playAtListenerPosition];
						
						match.shakedNum = 0;
						match.initDate = nil;
					}
					else if(fh.h_frame_id == 11)
					{
						[self _showAlert:@"Game finished" type:1];
						return;						
					}
					else if(fh.h_frame_id == 99)
					{
												
						// 헤더 뒤에 데이터 프레임이 있는 경우
						if(fd.d_frame_id == 1)
						{
							int frame_number = fd.d_frame_number;
							
							unsigned long client_time, latency;
							client_time = [self timeGetTime];
							
							latency = client_time - fh.h_time;
							/////////////////////////////						
//							NSLog([NSString stringWithFormat:@"[%i]%lu -> (%lu) -> [%i]%lu",frame_number, fh.h_time, latency, frameNumber, client_time]);
		
							
							int ball_owner = fd.d_ball_owner;
							int ball_shooter = fd.d_ball_shooter;
							float ball_pos_x = fd.d_ball_pos_x;
							float ball_pos_y = fd.d_ball_pos_y;
							double ball_dir = fd.d_ball_dir;
							
							int his_id = 0;
							float his_pos_x = fd.d_player0_pos_x;
							float his_pos_y = fd.d_player0_pos_y;
							double his_dir = fd.d_player0_dir;
//							PlayerStatus his_status = fd.d_player0_status;
							
							if(frame_number > preFrameNumber)
							{
								/* 스틸 사운드 재생처리 */
								int prevBallOwnerIndex = [[ClientManager sharedManager].match getBallOwner];
								Player* myself = [ClientManager sharedManager].match.myself;
								int myselfIndex = [[ClientManager sharedManager].match.players indexOfObject:myself];
								
								// 내(client)가 아닌 다른 누군가가 공을 갖고 있었고
								if (prevBallOwnerIndex != myselfIndex && prevBallOwnerIndex != -1)
									// 지금 공을 갖게 된 것이 나라면 스틸한 것이다.
									if (ball_owner == myselfIndex)
										[stealSound playAtListenerPosition];
								
								[[ClientManager sharedManager].match setPlayerPositionX:his_pos_x Y:his_pos_y At:his_id];
								[[ClientManager sharedManager].match setPlayerDirection:his_dir At:his_id];
								
								// [[ClientManager sharedManager].match setPlayerStatus:his_status At:his_id];
								
								[[ClientManager sharedManager].match setBallPositionX:ball_pos_x Y:ball_pos_y];
								[[ClientManager sharedManager].match setBallDirection:ball_dir];
								[[ClientManager sharedManager].match setBallOwner:ball_owner];
								[[ClientManager sharedManager].match setBallShooter:ball_shooter];
								
								[[ClientManager sharedManager].match setGameTime:frame_number];
								
								preFrameNumber = frame_number;
							}
						}
					}
				}
				// Server의 경우 (클라이언트에서 오는 메시지를 읽음)
				else
				{
					// 헤더 뒤에 데이터 프레임이 있는 경우
					if(fh.h_frame_id == 99)
					{
						if(fd.d_frame_id == 2)
						{
							// 1대 1을 가정
							int his_id = 1;
							float his_pos_x = fd.d_player0_pos_x;
							float his_pos_y = fd.d_player0_pos_y;
							double his_dir = fd.d_player0_dir;
//							PlayerStatus his_status = fd.d_player0_status;
							
							[[ClientManager sharedManager].match setPlayerPositionX:his_pos_x Y:his_pos_y At:his_id];
							[[ClientManager sharedManager].match setPlayerDirection:his_dir At:his_id];
							
							// 플레이어 상태는 네트워크를 통해 설정하지 않도록 한다.
							//[[ClientManager sharedManager].match setPlayerStatus:his_status At:his_id];
							
						}
					}
					// 클라이언트가 슛한 경우
					else if (fh.h_frame_id == 6)
					{
						// 슛 정보 갱신
						[[ClientManager sharedManager].match setBallShooter: [[ClientManager sharedManager].match getBallOwner]];
						[[ClientManager sharedManager].match setBallDirection:[[ClientManager sharedManager].match getPlayerDirectionAt:1]];
					}
					// 클라이언트가 스틸한 경우
					else if (fh.h_frame_id == 7)
					{
						Match* match = [ClientManager sharedManager].match;
						// 1대1 가정하므로 스틸 시도한 클라이언트의 index는 무조건 1이다.
						int clientIndex = 1;
						Player* client = [match.players objectAtIndex:clientIndex]; 
						
						int ballOwnerIndex = [match getBallOwner];
						Player *ballOwner = [match.players objectAtIndex:ballOwnerIndex];
						
						double distance = [client getDistanceFrom:client.position To:ballOwner.position];
						
						if (distance <= stealRange)
							[match setBallOwner: clientIndex];
					}
					// 클라이언트가 스킬을 사용한 경우
					else if (fh.h_frame_id == 8)
					{
						Match* match = [ClientManager sharedManager].match;
						int clientIndex = 1;
						Player* client = [match.players objectAtIndex:clientIndex];
						
						// 스킬을 사용한 플레이어 근처의 선수들 파악
						NSMutableArray *players = match.players;
						
						for (Player *tempPlayer in players)
						{
							if (tempPlayer.status == Stun)
								continue;
							if (tempPlayer == client)
								continue;
							if ([tempPlayer isKindOfClass:[GoalKeeper class]])
								continue;
							
							double distance = [tempPlayer getDistanceFrom:tempPlayer.position To:client.position];
							
							if (distance <= stunRange)
							{
								if(tempPlayer.status != Stun)
								{
									// 해당 선수들의 상태를 스턴으로 바꾼다.
									tempPlayer.status = Stun;
									tempPlayer.stunDurationByFrame = stunDuration;
									
									// 클라이언트에게 스턴이 적용되었음을 알려준다.
									struct FrameHeader fh;
									
									fh.h_frame_id		= 10;				// Header Message가 아닐 경우
									fh.h_continue		= NO;
									
									[stunSound playAtListenerPosition];
									
									NSOutputStream *oStream = [outputStreams objectAtIndex:clientIndex];
									[self sendHeader:&fh toStream:oStream];
								}
							}
						}
						
						//match.shakedNum = 0;
						//match.initDate = nil;
					}
				}
			}
			break;
		}
		case NSStreamEventEndEncountered:
		{
			// 통신이 끝난 경우 처리
			alertView = [[UIAlertView alloc] initWithTitle:@"Peer Disconnected!" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:@"Continue", nil];
			[alertView show];
			[alertView release];
			
			[self initializeGame];
			
			break;
		}
	}
}

@end

@implementation NetworkManager (TCPServerDelegate)

- (void)serverDidEnableBonjour:(TCPServer*)server withName:(NSString*)string
{
	NSLog(@"%s", _cmd);
	[self presentLobby:string];
}

// Server 역할
// 다른 플레이어로부터 접속 요청이 들어오면 해당 플레이어의 스트림을 저장하고 오픈
- (void)didAcceptConnectionForServer:(TCPServer*)server inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr
{
	
	if (server != _server)
		return;
	
	int index = ++numberOfPlayer;
	[inputStreams insertObject:istr atIndex:index];
	[outputStreams insertObject:ostr atIndex:index];
	[inputReady insertObject:@"NO" atIndex:index];
	[outputReady insertObject:@"NO" atIndex:index];
	
	//	[istr retain];
	//	[ostr retain];
	
	[self openInputStream:istr outputStream:ostr];
}
@end
